/*
 Xern Entity Relocation Network
 Copyright (C) 2010-2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#include <stdio.h>
#include "tinyxml.h"
#include "Xern/IResourceCache.hpp"
#include "Xern/IResourceLoadedCallback.hpp"
#include "Xern/IResourceListener.hpp"
#include "Xern/FileSystem.hpp"
#include "Xern/Exceptions.hpp"
#include "Xern/Log.hpp"
#include "Xern/ResourceManager.hpp"
#include "Xern/Engine.hpp"

namespace Xern
{
	ResourceManager::ResourceManager()
	{
	}

	ResourceManager::~ResourceManager()
	{
	}

	boost::shared_ptr<IResourceCache> ResourceManager::GetResourceCache()
	{
		return resourceCache;
	}

	void ResourceManager::SetResourceCache(boost::shared_ptr<IResourceCache> cache)
	{
		resourceCache = cache;
		if(resourceCache)
			resourceCache->Initialize();
	}

	void ResourceManager::Shutdown()
	{
		if(resourceCache)
		{
			resourceCache->Shutdown();
			resourceCache.reset();
		}
	}

	boost::shared_ptr<ResourceGroup>
	ResourceManager::GetGroup(const std::string &name)
	{
		GroupsType::const_iterator it = groups.find(name);
		if(it != groups.end())
			return it->second;
		return boost::shared_ptr<ResourceGroup> ();
	}

	boost::shared_ptr<ResourceGroup>
	ResourceManager::CreateGroup(const std::string &name)
	{
		// Try to return an existing group with the same name.
		boost::shared_ptr<ResourceGroup> ret(GetGroup(name));

		if(!ret)
		{
			// Create a new group and store it.
			ret.reset(new ResourceGroup(name));
			groups.insert(std::make_pair(name, ret));
		}

		return ret;
	}

	const ResourceManager::GroupsType &ResourceManager::GetGroups() const
	{
		return groups;
	}

	void ResourceManager::LoadGroups(const std::string &path)
	{
		TiXmlDocument doc;
		if(!doc.LoadFile(path))
			throw XernException("Failed to read file '" + path + "': "
						+ doc.ErrorDesc());

		// Get the top element.
		TiXmlElement *root = doc.RootElement();
		if(!root)
			throw XernException("Resources descriptor '" + path +
								"' is empty.");

		// Get the groups element.
		TiXmlElement *groups = root->FirstChildElement("groups");
		if(!groups)
			throw XernException("Resources descriptor '" + path +
					"' doesn't have <groups> element.");

		// Extract the base path.
		std::string basepath = DirName(path);

		// Read each group.
		TiXmlElement *groupElement = groups->FirstChildElement("group");
		for(; groupElement; groupElement = groupElement->NextSiblingElement("group"))
		{
			// Get the group name.
			const char *name = groupElement->Attribute("name");

			// Ignore groups without name.
			if(!name)
				continue;

			// Create the resource group.
			boost::shared_ptr<ResourceGroup> group(CreateGroup(name));

			// Read each directory.
			TiXmlElement *directory = groupElement->FirstChildElement();
			for(; directory; directory = directory->NextSiblingElement())
			{
				const char *dirpath = directory->Attribute("path");
				if(!dirpath )
					continue;

				std::string fullpath = dirpath;
				if(!IsAbsolutePath(fullpath))
					fullpath = JoinPath(basepath, fullpath);

				// Check the directory type.
				if(directory->ValueStr() == "filesystem")
				{
					group->AddDirectory(fullpath, ResourceGroup::DT_FILESYSTEM);
				}
				else if(directory->ValueStr() == "zip")
				{
					group->AddDirectory(fullpath, ResourceGroup::DT_ZIP);
				}
				else
				{
					// Print an error.
					fprintf(stderr, "unknown directory type <%s> in resource '%s'\n",
							directory->Value(), path.c_str());
				}

			}
		}

		// Get the optional references element.
		TiXmlElement *references = root->FirstChildElement("references");
		if(references)
		{
			// Read each reference.
			TiXmlElement *ref = references->FirstChildElement("ref");
			for(; ref; ref = ref->NextSiblingElement("ref"))
			{
				// Get the path.
				const char *path = ref->Attribute("path");
				if(!path)
					continue;

				// Expand the reference path.
				std::string refpath = path;
				if(!IsAbsolutePath(refpath))
					refpath = JoinPath(basepath, refpath);

				// Load the reference, but ignore the exceptions.
				try
				{
					LoadGroups(refpath);
				}
				catch(XernException &e)
				{
					fprintf(stderr, "%s\n", e.what());
				}
			}
		}

	}

	std::string ResourceManager::FindResourcePath(const std::string &name, const std::string &group)
	{
		// Find the resource group.
		boost::shared_ptr<ResourceGroup> g = GetGroup(group);
		if(!g)
			return ""; // Return an empty string.

		// Check in each one of the directories of the group.
		const ResourceGroup::Directories &folders = g->GetDirectories();
		for(size_t i = 0; i < folders.size(); i++)
		{
			const ResourceGroup::DirectoryData &dir = folders[i];
			if(dir.second == ResourceGroup::DT_ZIP)
			{
				LogConsole->Warning("Unsupported zip folder '%s'", dir.first.c_str());
			}
			else if(dir.second == ResourceGroup::DT_FILESYSTEM)
			{
				// Check if the resource exists.
				std::string fullpath = JoinPath(dir.first, name);
				if(IsFile(fullpath))
					return fullpath;
			}
			else
			{
				LogConsole->Warning("Unsupported folder[type=%d] '%s'", dir.second, dir.first.c_str());
			}
		}

		// Couldn't find the resource.
		return "";
	}

	void ResourceManager::RegisterResourceLoader(boost::shared_ptr<IResourceLoader> loader)
	{
		loaders.insert(loader);
	}

	void ResourceManager::UnregisterResourceLoader(boost::shared_ptr<IResourceLoader> loader)
	{
		loaders.erase(loader);
	}

	void ResourceManager::RegisterResourceListener(boost::shared_ptr<IResourceListener> listener)
	{
		listeners.insert(listener);
	}

	void ResourceManager::UnregisterResourceListener(boost::shared_ptr<IResourceListener> listener)
	{
		listeners.erase(listener);
	}

	void ResourceManager::OnAddedResource(boost::shared_ptr<Entity> entity, boost::shared_ptr<IResource> resource)
	{
		listenersType::const_iterator it = listeners.begin();
		for(; it != listeners.end(); it++)
		{
			boost::shared_ptr<IResourceListener> listener = *it;
			listener->OnAddedResource(entity, resource);
		}

		usedResources.insert(UsedResource(entity, resource));
	}

	void ResourceManager::OnRemovedResource(boost::shared_ptr<Entity> entity, boost::shared_ptr<IResource> resource)
	{
		listenersType::const_iterator it = listeners.begin();
		for(; it != listeners.end(); it++)
		{
			boost::shared_ptr<IResourceListener> listener = *it;
			listener->OnRemovedResource(entity, resource);
		}

		usedResources.erase(UsedResource(entity, resource));
	}

	const ResourceManager::UsedResources &ResourceManager::GetUsedResources() const
	{
		return usedResources;
	}

	boost::shared_ptr<IResource> ResourceManager::LoadFromCache(const Sha256 &hash)
	{
		if(!resourceCache)
			return boost::shared_ptr<IResource> ();

		// Find the path in the cache.
		std::string fullpath = resourceCache->GetCachePath(hash);
		if(!fullpath.empty() && IsFile(fullpath))
		{
			return LoadFromCache(fullpath);
		}

		return boost::shared_ptr<IResource> ();
	}

	boost::shared_ptr<IResource> ResourceManager::LoadFromCache(const uuid &id)
	{
		if(!resourceCache)
			return boost::shared_ptr<IResource> ();

		// Find the path in the cache.
		std::string fullpath = resourceCache->GetCachePath(id);
		if(!fullpath.empty() && IsFile(fullpath))
		{
			return LoadFromCache(fullpath);
		}

		return boost::shared_ptr<IResource> ();
	}

	boost::shared_ptr<IResource> ResourceManager::LoadFromCache(const std::string &fullpath)
	{
		// Ask the cache for already loaded resources.
		boost::shared_ptr<IResource> resource = resourceCache->LoadResource(fullpath);
		if(resource)
			return resource;

		// Load the resource.
		loadersType::const_iterator it = loaders.begin();
		for(; it != loaders.end(); it++)
		{
			boost::shared_ptr<IResourceLoader> loader = *it;
			if(loader->CanLoadResource(fullpath, "Cache"))
			{
				resource = loader->LoadResource(fullpath, "Cache", fullpath);
				if(resource)
				{
					// Notify the resource cache.
					resourceCache->ResourceLoaded(resource, fullpath);

					// Read the resource id or generate it.
					uuid id = resourceCache->GetResourceId(fullpath);
					if(id.is_nil())
					{
						id = idGenerator();
						resourceCache->RegisterResourceId(id, fullpath);
					}

					// Set the resource id.
					resource->SetId(id);
					RenameResource(resource, resource->GetId());

					// Return the resource.
					return resource;
				}
			}
		}

		throw XernException("Couldn't find a loader for " + fullpath);
	}

	boost::shared_ptr<IResource> ResourceManager::LoadFromMemory(const std::string &mime, boost::shared_ptr<IDataFilter> data)
	{
		loadersType::const_iterator it = loaders.begin();
		for(; it != loaders.end(); it++)
		{
			boost::shared_ptr<IResourceLoader> loader = *it;
			if(loader->CanLoadResource(mime))
			{
				boost::shared_ptr<IResource> resource = loader->LoadResource(mime, data);
				if(!resource)
					continue;

				// Generate the resource id and register it.
				resource->SetId(idGenerator());
				RenameResource(resource, resource->GetId());
				return resource;
			}
		}

		throw XernException("Couldn't find a loader for [" + mime + "]");
	}

	bool ResourceManager::DelayedLoad(const uuid &id, boost::shared_ptr<IResourceLoadedCallback> callback)
	{
		assert(callback);

		// Try with already loaded resources.
		boost::shared_ptr<IResource> resource = FindResource(id);
		if(resource)
		{
			callback->OnLoadedResource(resource);
			return true;
		}

		// Try to load from the cache.
		resource = LoadFromCache(id);
		if(resource)
		{
			callback->OnLoadedResource(resource);
			return true;
		}

		// Pass the work into the listeners.
		listenersType::iterator it = listeners.begin();
		for(; it != listeners.end(); it++)
		{
			boost::shared_ptr<IResourceListener> listener = *it;
			if(listener->OnDelayedLoad(id, callback))
				return true;
		}

		// Couldn't be handled.
		return false;
	}

	boost::shared_ptr<IResource> ResourceManager::LoadResource(const std::string &filename, const std::string &group)
	{
		std::string fullpath;
		bool fromCache = false;

		if(resourceCache)
		{
			// Get the cache path.
			fullpath = resourceCache->GetCachePath(filename, group);
			fromCache = IsFile(fullpath);

			// Avoid reloading already loaded resources.
			if(fromCache)
			{
				boost::shared_ptr<IResource> resource = resourceCache->LoadResource(fullpath);
				if(resource)
					return resource;
			}
		}

		if(!fromCache)
			fullpath = FindResourcePath(filename, group);

		loadersType::const_iterator it = loaders.begin();
		for(; it != loaders.end(); it++)
		{
			boost::shared_ptr<IResourceLoader> loader = *it;
			if(loader->CanLoadResource(filename, group))
			{
				boost::shared_ptr<IResource> resource = loader->LoadResource(filename, group, fullpath);
				if(!resource)
					continue;

				if(resourceCache && !fromCache)
				{
					// Register first time resources with the cache.
					resourceCache->RegisterResourceName(filename, group, fullpath);
					resourceCache->RegisterResourceFile(fullpath);
				}

				uuid id = resource->GetId();
				if(resourceCache)
				{
					// Register loaded resources.
					resourceCache->ResourceLoaded(resource, fullpath);

					// Read the resource id
					if(id.is_nil())
						id = resourceCache->GetResourceId(fullpath);
				}

				// Generate a resource id
				if(id.is_nil())
				{
					id = idGenerator();
					if(resourceCache)
						resourceCache->RegisterResourceId(id, fullpath);
				}

				// Generate the resource id and register.
				resource->SetId(id);
				RenameResource(resource, resource->GetId());
				return resource;
			}
		}

		throw XernException("Couldn't find a loader for [" + group + "]" + filename);
	}

	boost::shared_ptr<IResource> ResourceManager::FindResource(const uuid &id)
	{
		resourceMapType::iterator it = resourceMap.find(id);
		if(it != resourceMap.end())
			return it->second.lock();
		return boost::shared_ptr<IResource> ();
	}

	void ResourceManager::RegisterExternalResource(boost::shared_ptr<IResource> resource)
	{
		if(resource->GetId().is_nil())
			resource->SetId(idGenerator());
		RenameResource(resource, resource->GetId());
	}

	void ResourceManager::RenameResource(boost::shared_ptr<IResource> resource, const uuid &id)
	{
		if(resourceCache)
			resourceCache->RenameResource(resource->GetId(), id);

		resourceMapType::iterator it = resourceMap.find(resource->GetId());
		if(it != resourceMap.end())
			resourceMap.erase(it);

		resource->SetId(id);
		resourceMap.insert(std::make_pair(id, resource));
	}

	void ResourceManager::UpdateState(boost::shared_ptr<IResource> resource, boost::shared_ptr<IDataFilter> stateData)
	{
		listenersType::const_iterator it = listeners.begin();
		for(; it != listeners.end(); it++)
		{
			boost::shared_ptr<IResourceListener> listener = *it;
			listener->OnUpdateState(resource, stateData);
		}
	}

	void ResourceManager::LoadPlugins()
	{
		// Find the resource loader group.
		boost::shared_ptr<PluginRegistry> reg = Engine::Get().GetPluginRegistry();
		const PluginGroup *group = reg->QueryGroup("ResourceLoader");
		if(!group)
			return;

		// Register the IResourceLoader interfaces.
		PluginGroup::const_iterator it = group->begin();
		for(; it != group->end(); it++)
		{
			// Check if it is a resource loader.
			boost::shared_ptr<IInterface> interface = it->second;
			boost::shared_ptr<IResourceLoader> loader =
					boost::dynamic_pointer_cast<IResourceLoader> (interface);

			// Register the loader.
			if(loader)
				RegisterResourceLoader(loader);
		}
	}
}; // namespace Xern
